package com.whales.shared.util;

import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;

/**
 * Calendar 관련 Util Class
 * 
 * @author swpark 2007.10.23
 * @since SIMS V3.0
 * @version 2007.10.23
 */
public class CalendarUtil {
    private Calendar viewDate;

    private int maxDay; // 총 일수

    private int weeks; // 총 주수

    private int[][] days; //

    private int startWeek; // 1일의 주 index(0:일, 1:월 )

    /**
     * 현재일의 Calendar 생성
     */
    public CalendarUtil() {
        viewDate = Calendar.getInstance();
        setViewDate();
    }

    /**
     * 해당년,월에 해당하는 Calendar생성 
     */
    public CalendarUtil(String year, String month) {
        viewDate = Calendar.getInstance();
        viewDate.set(Integer.parseInt(year), Integer.parseInt(month) - 1, 1);
        setViewDate();
    }

    /**
     * 해당년,월,일에 해당하는 Calendar생성 
     */
    public CalendarUtil(int year, int month, int date) {
        viewDate = Calendar.getInstance();
        viewDate.set(year, month - 1, date);
        setViewDate();
    }

    /**
     * ISO9601, weeks, maxDay Set
     */
    private void setViewDate() {

        weeks = viewDate.getActualMaximum(Calendar.WEEK_OF_MONTH);

        /*
         * 해당월의 1일에 대한 요일이 구한다. 이미 Calendar의 일자는 1일로 설정되어 있다. 배열의 인텍스 때문에 1을 빼준다.
         */
        startWeek = viewDate.get(Calendar.DAY_OF_WEEK) - 2;
        /* 일요일인 경우 마지막이로 처리 */
        if (startWeek == -1)
            startWeek = 6;
        maxDay = viewDate.getActualMaximum(Calendar.DAY_OF_MONTH); // 달의 총일수

        /*
         * 화면에 보여줄 형식으로 일자를 배치한다. days[6][7] 휴일 목록과 비교하여 해당일이 휴일인지 아닌지 여부도 구한다. 한주의 시작인 월요일부터
         * 
         */
        if (startWeek == 6)
            weeks++;
        if ((startWeek + maxDay) % 7 == 0)
            weeks--;

        viewDate.setFirstDayOfWeek(2); // 주의 첫날은 월요일이다.
        viewDate.setMinimalDaysInFirstWeek(4); // 첫 Thursday 가 포함된 주가 첫주 이므로,최소한

    }

    public int getWeeks() {
        return weeks;
    }

    /**
     * 달력형식으로 일자 배열
     * 
     * @return int[주수][7]
     * 
     */
    public int[][] getDays() {
        days = new int[weeks][7];// = CalendarUtil.getDays(weeks, startWeek, maxDay);
        int day = 1;

        /*
         * 일자를 배열에 채운다. 일수가 들어가지 않는곳은 '0'으로 셋팅된다.
         */
        /* 해당 월의 주차 증가 (0~총 주-1) */
        for (int i = 0; i < weeks; i++) {
            for (int j = 0; j < 7; j++) {
                if ((i == 0) && (j < startWeek))
                    continue;
                else if (day > maxDay)
                    break;
                days[i][j] = day++;
            }
        }
        return days;
    }

    /**
     * 해당 월의 주차 목록
     */
    public String[] getWeekOfYear() {

        DecimalFormat df = new DecimalFormat("00");
        int firstWeekOfYear = getWeekOfYear(viewDate.get(Calendar.YEAR), viewDate.get(Calendar.MONTH), 1); // 이달의 첫주의 주차
        int lastWeekOfYear = getWeekOfYear(viewDate.get(Calendar.YEAR), viewDate.get(Calendar.MONTH), viewDate.getActualMaximum(Calendar.DAY_OF_MONTH)); // 이달의 마지막주의 주차
        int beforeLastWeekOfYear = getWeekOfYear(viewDate.get(Calendar.YEAR), viewDate.get(Calendar.MONTH), viewDate.getActualMaximum(Calendar.DAY_OF_MONTH) - 7);
        int nextFirstWeekOfYear = getWeekOfYear(viewDate.get(Calendar.YEAR), viewDate.get(Calendar.MONTH), viewDate.getActualMinimum(Calendar.DAY_OF_MONTH) + 7);

        /*
         * 해당 년월의 주차 목록을 구한다.
         * 
         */
        int length = (beforeLastWeekOfYear + 1) - (nextFirstWeekOfYear - 1) + 1;
        StringBuffer[] weekOfYearBuffer = new StringBuffer[length];
        String[] weekOfYear = new String[length];

        boolean flag = false;

        for (int i = 0; i < length; i++) {
            weekOfYearBuffer[i] = new StringBuffer();

            if ((firstWeekOfYear > 50) && i == 0) { // 1월이면서 지난해 마지막 주가 연결되는 경우
                weekOfYearBuffer[i].append(viewDate.get(Calendar.YEAR) - 1);
                weekOfYearBuffer[i].append(firstWeekOfYear);
                weekOfYearBuffer[i].append(i);
                firstWeekOfYear = 1; // 1월이 1주차부터 시작하지 않는 해는 firstWeekOfYear가 1이 아니므로 1로 변경..
                flag = true; // 1월이 1주차부터 시작하지 않는 해는 else조건에서 index 변경을 위해 flag 변경.
            } else if (lastWeekOfYear == 1 && i == (length - 1)) { // 12월이면서 다음해 첫주가 연결되는 경우
                weekOfYearBuffer[i].append(viewDate.get(Calendar.YEAR) + 1);
                weekOfYearBuffer[i].append(df.format(lastWeekOfYear));
                weekOfYearBuffer[i].append(0);
            } else {
                weekOfYearBuffer[i].append(viewDate.get(Calendar.YEAR));
                weekOfYearBuffer[i].append(df.format(firstWeekOfYear));
                weekOfYearBuffer[i].append(flag ? i - 1 : i);
            }

            weekOfYear[i] = new String();
            weekOfYear[i] = weekOfYearBuffer[i].toString();
        }

        return weekOfYear;
    }

    // array를 list로
    public List<String> getWeekOfYear2List() {
        return Arrays.asList(this.getWeekOfYear());
    }

    /**
     * 해당 연도를 YYYY형식으로 리턴한다.
     * @return YYYY형식의 년도
     */
    public String getYYYY() {
        String year = String.valueOf(viewDate.get(Calendar.YEAR));
        return year;
    }

    /**
     * 해당 월을 리턴한다.
     * @return 월 리턴 (예 1, 10)
     */
    public String getMM() {
        String month = String.valueOf(viewDate.get(Calendar.MONTH) + 1);
        return month;
    }

    /**
     * 해당일을 리턴한다.
     * 현재일(CalendarUtil())로 생성된 경우 현재 일자가
     * CalendarUtil(String year, String month)로 생성된 경우는 1리 리턴된다.
     * @return 일자
     */
    public String getDD() {
        String day = String.valueOf(viewDate.get(Calendar.DAY_OF_MONTH));
        return day;
    }

    /**
     * 해당 년월을  YYYYMM형식으로 리턴한다.
     * @return YYYYMM형식의 년월
     */
    public String getYYYYMM() {
        String yearMonth = "";
        String year = getYYYY();
        String month = getMM();

        if (month.length() == 1)
            yearMonth = year + "0" + month;
        else
            yearMonth = year + month;

        return yearMonth;
    }

    /**
     * 날짜 형식에 따라 생성된 날짜형태로 반환한다.
     * 
     * @param format
     * @return
     */
    public static String getDateFormat(String format) {
        Calendar calendar = Calendar.getInstance();

        /* ISO 9601로 셋팅 */
        calendar.setFirstDayOfWeek(2); // 주의 첫날은 월요일이다.
        calendar.setMinimalDaysInFirstWeek(4); // 첫 Thursday 가 포함된 주가 첫주 이므로, 최소한 한주에 4일이 있어야한다.

        DateFormat dateFormat = new SimpleDateFormat(format); // 날짜 형식 지정
        return dateFormat.format(calendar.getTime());
    }
    
    /**
     * 날짜 형식과 locale 설정에 따라 생성된 날짜형태로 반환한다.
     * 
     * @param format
     * @param locale
     * @return	
     *
     */
    public static String getDateFormat(String format, String locale){
        Calendar calendar = Calendar.getInstance();

        /* ISO 9601로 셋팅 */
        calendar.setFirstDayOfWeek(2); // 주의 첫날은 월요일이다.
        calendar.setMinimalDaysInFirstWeek(4); // 첫 Thursday 가 포함된 주가 첫주 이므로, 최소한 한주에 4일이 있어야한다.

        DateFormat dateFormat = new SimpleDateFormat(format, new Locale(locale)); // 날짜 형식 지정
        return dateFormat.format(calendar.getTime());
    }

    /**
     * 현재날자의 주차 리턴한다.
     * @return 현재 주차
     */
    public String getYearWeek() {
        if (viewDate.get(Calendar.WEEK_OF_YEAR) < 10)
            return viewDate.get(Calendar.YEAR) + "0" + String.valueOf(viewDate.get(Calendar.WEEK_OF_YEAR));
        else
            return viewDate.get(Calendar.YEAR) + String.valueOf(viewDate.get(Calendar.WEEK_OF_YEAR));
    }

    /**
     * 월의 키값 구하는 함수
     * key는 resoruce.Lebel.properties파일에 존재 한다.
     * key : calendar.month.월
     * @return 생성된 key
     */
    public String getMonthKey() {
        int monthIdx = Integer.parseInt(getMM());
        String monthKey = "calendar.";

        switch (monthIdx) {
        case 1:
            monthKey += "January";
            break;
        case 2:
            monthKey += "February";
            break;
        case 3:
            monthKey += "March";
            break;
        case 4:
            monthKey += "April";
            break;
        case 5:
            monthKey += "May";
            break;
        case 6:
            monthKey += "June";
            break;
        case 7:
            monthKey += "July";
            break;
        case 8:
            monthKey += "August";
            break;
        case 9:
            monthKey += "September";
            break;
        case 10:
            monthKey += "October";
            break;
        case 11:
            monthKey += "November";
            break;
        default:
            monthKey += "December";
            break;
        }

        return monthKey;
    }

    /**
     * 요일의 키값 구하는 함수
     * key는 resoruce.Lebel.properties파일에 존재 한다.
     * key : calendar.week.요일의key
     * 요일 key : 1:일, 2:월 ~ 7:토
     * @return 생성된 key
     */
    public String getWeekKey() {
        int weekIdx = viewDate.get(Calendar.DAY_OF_WEEK);
        String weekKey = "calendar.";

        switch (weekIdx) {
        case 1:
            weekKey += "Sun";
            break;
        case 2:
            weekKey += "Mon";
            break;
        case 3:
            weekKey += "Twe";
            break;
        case 4:
            weekKey += "Wed";
            break;
        case 5:
            weekKey += "Thu";
            break;
        case 6:
            weekKey += "Fri";
            break;
        default:
            weekKey += "Sat";
            break;
        }

        return weekKey;
    }
    
    /**
     * 요일의 index값을 반환한다.(1:일, 2:월 ~ 7:토)
     * @return
     */
    public int getWeekByIndex(){
    	return viewDate.get(Calendar.DAY_OF_WEEK);
    }

    /** 
     * 파라메터의 날자에 해당하는 주차 계산
     * prameter
     *     year:년도
     *     month:월 (Calendar.MONTH 또는 int형으로 입력할땐 -1한 월을 입력한다.) --> 이유는 이미 혼용해서 사용하였기 때문에. 
     *     day:일
     * return
     *     주차
     */
    public static int getWeekOfYear(int year, int month, int day) {
        /* 해당 날자의 calendar 생성 */
        Calendar calendar = Calendar.getInstance();
        calendar.set(year, month, day);

        /* ISO 9601로 셋팅 */
        calendar.setFirstDayOfWeek(2); // 주의 첫날은 월요일이다.
        calendar.setMinimalDaysInFirstWeek(4); // 첫 Thursday 가 포함된 주가 첫주 이므로, 최소한 한주에 4일이 있어야한다.

        return calendar.get(Calendar.WEEK_OF_YEAR);
    }

    /**
     * 해당 주차의 시작일과 종료일
     * 
     * prameter : 주차 (200628)
     * reutnr : 해당주차의 월~일요일( 2006-07-10 ~ 2006-07-16)
     */
    public static String getWeekFirstDateAndLastDate(String yearweek, String format) {
        return getWeekFirstDateAndLastDate(Integer.parseInt(yearweek.substring(0, 4)), Integer.parseInt(yearweek.substring(4)), format);
    }

    /**
     * 년도와 주차를 받아 주차에 해당하는 기간을 리턴한다.
     * 월요일부터 일요일까지.
     * 
     * @param year 년도
     * @param week 주차
     * @param format "YYYY-MM-DD","MM-DD"
     * @return 기간
     */
    public static String getWeekFirstDateAndLastDate(int year, int week, String format) {
        String MonDay = "";
        String SunDay = "";

        Calendar now = Calendar.getInstance();
        now.setFirstDayOfWeek(2);
        now.setMinimalDaysInFirstWeek(4);
        now.set(year, Calendar.JANUARY, 1);

        int[] convert_monday = { 6, 0, 1, 2, 3, 4, 5 }; // cal.add(-) 월요일 세팅시.

        int WEEK_OF_YEAR = now.get(java.util.Calendar.WEEK_OF_YEAR);
        int DAY_OF_WEEK = now.get(java.util.Calendar.DAY_OF_WEEK);

        // 월요일 세팅.
        if (WEEK_OF_YEAR > 1) {
            now.add(Calendar.DATE, (7 * week) - convert_monday[DAY_OF_WEEK - 1]);
        } else {
            now.add(Calendar.DATE, (7 * (week - 1)) - convert_monday[DAY_OF_WEEK - 1]);
        }

        String weekYear = Integer.toString(now.get(Calendar.YEAR));
        String weekMonth = Integer.toString(now.get(Calendar.MONTH) + 1);
        String weekDay = Integer.toString(now.get(Calendar.DAY_OF_MONTH));
        if (now.get(Calendar.MONTH) + 1 < 10)
            weekMonth = "0" + Integer.toString(now.get(Calendar.MONTH) + 1);
        if (now.get(Calendar.DAY_OF_MONTH) < 10)
            weekDay = "0" + Integer.toString(now.get(Calendar.DAY_OF_MONTH));

        if (format.equals("MM-DD")) {
            MonDay = weekMonth + "-" + weekDay;
        } else {
            MonDay = weekYear + "-" + weekMonth + "-" + weekDay;
        }

        now.add(Calendar.DATE, 6); // 해당주차의 일요일을 가져오기위해 +6처리
        weekYear = Integer.toString(now.get(Calendar.YEAR));
        weekMonth = Integer.toString(now.get(Calendar.MONTH) + 1);
        weekDay = Integer.toString(now.get(Calendar.DAY_OF_MONTH));
        if (now.get(Calendar.MONTH) + 1 < 10)
            weekMonth = "0" + Integer.toString(now.get(Calendar.MONTH) + 1);
        if (now.get(Calendar.DAY_OF_MONTH) < 10)
            weekDay = "0" + Integer.toString(now.get(Calendar.DAY_OF_MONTH));

        if (format.equals("MM-DD")) {
            SunDay = weekMonth + "-" + weekDay;
        } else {
            SunDay = weekYear + "-" + weekMonth + "-" + weekDay;
        }

        return MonDay + " ~ " + SunDay;
    }

    /**
     * 그 주의 시작일(월요일)날짜를 찾아주는 메쏘드
     * 
     * @return Calendar 그 주의 시작일(월요일)
     */
    public static Calendar getMondayOfWeek(int Year, int Week) {
        // - 2007.04.13 swpark 날짜 이상으로 수정
        int convert[] = { 6, 7, 1, 2, 3, 4, 5 }; // 일,월,화,수,목,금,토의 값이며 값의 산출방법은 문서를 참조하도록한다.

        Calendar StartDate = Calendar.getInstance();

        StartDate.set(Calendar.YEAR, Year);
        StartDate.set(Calendar.MONTH, Calendar.JANUARY);
        StartDate.set(Calendar.DATE, 1);

        StartDate.add(Calendar.DATE, 7 * Week - convert[(StartDate.get(Calendar.DAY_OF_WEEK) - 1)]);

        return StartDate;
    }

    public static Calendar getMondayOfWeek(Calendar cal) {
        return getMondayOfWeek(cal.get(Calendar.YEAR), cal.get(Calendar.WEEK_OF_YEAR));
    }

    public static Calendar getMondayOfWeek(String Year, String Week) {
        return getMondayOfWeek(Integer.parseInt(Year), Integer.parseInt(Week));
    }

    /**
     * 해당월의 각각의 주차별로 해당 주차가 몇주차인지 알아온다.
     * 
     * @param year
     * @param month
     * @return 해당월의 주차 리스트
     */
    public int[] getWeeksOfMonth(int year, int month) {
        int firstWeekOfYear = CalendarUtil.getWeekOfYear(year, month, 1);
        int lastWeekOYear = CalendarUtil.getWeekOfYear(year, month, viewDate.getActualMaximum(Calendar.DAY_OF_MONTH));
        int lastTemp = lastWeekOYear;

        // 12월의 마지막주가 1일경우처리.
        if (lastWeekOYear == 1) {
            lastTemp = CalendarUtil.getWeekOfYear(year, month, viewDate.getActualMaximum(Calendar.DAY_OF_MONTH) - 7) + 1;
        }

        int weekSize = lastTemp - firstWeekOfYear + 1;

        if (month == 0 && firstWeekOfYear >= 52) {
            weekSize = lastTemp + 1;
        }
        int[] weeksOfMonth = new int[weekSize];

        for (int i = 0; i < weekSize; i++) {
            weeksOfMonth[i] = firstWeekOfYear++;
            // 1월의 첫주가 1주차가 아닐 경우 또는 12월의 마지막주가 1주차일경우처리.
            if ((month == 0 && firstWeekOfYear > 52) || (i == weekSize - 2 && lastWeekOYear == 1)) {
                firstWeekOfYear = 1;
            }
        }

        return weeksOfMonth;
    }

    /**
     * String으로 받은 년월을 int형으로 변환하여 해당월의 주차 정보를 리턴한다.
     * 
     * @param year
     * @param month
     * @return 해당월의 주차 리스트
     */
    public int[] getWeeksOfMonth(String year, String month) {
        int iYear = Integer.parseInt(year);
        int iMonth = Integer.parseInt(month);

        return this.getWeeksOfMonth(iYear, iMonth);
    }

    /**
     * 해당 년월의 주차 정보를 리턴한다.
     * @return
     */
    public int[] getWeeksOfMonth() {
        return this.getWeeksOfMonth(viewDate.get(Calendar.YEAR), viewDate.get(Calendar.MONTH));
    }

    /**
     * Weeklyreport 작성에서 사용하는 현재 년월의 주차정보를 가져온다.
     * 단 12월의 마지막이 1일 경우 제거 한다. 
     * @return 현재 년월의 주차
     */
    public int[] getWeeksOfMonth4WeeklyReport() {
        String month = this.getMM();
        int[] week = this.getWeeksOfMonth();

        if (Integer.parseInt(month) == 1 && week[0] > 1) {// 1월의 주차에 12월 주차가 포함된경우 12월 주차 제거.
            int[] temp = new int[week.length - 1];
            for (int i = 1; i < week.length; i++) {
                temp[i - 1] = week[i];
            }
            week = temp;
        } else if (Integer.parseInt(month) == 12 && week[week.length - 1] < 2) {// 12월주차에 1월주차가 포함된경우 제거.
            int[] temp = new int[week.length - 1];
            for (int i = 0; i < week.length - 1; i++) {
                temp[i] = week[i];
            }
            week = temp;
        }

        return week;
    }

    /**
     * Weeklyreport 작성에서 사용하는 현재 년월의 주차 기간
     * @return 주차 기간
     */
    public String[] getWeeksOfMonthDate4WeeklyReport() {
        int[] week = this.getWeeksOfMonth4WeeklyReport();

        String[] weekOfMonthDate = new String[week.length];
        for (int i = 0; i < week.length; i++) {
            weekOfMonthDate[i] = CalendarUtil.getWeekFirstDateAndLastDate(this.getYYYY() + week[i], "MM-DD");
        }
        return weekOfMonthDate;
    }
}
